package main;

import static crypt.EncryptUtil.*;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import crypt.ConfigContainer;
import crypt.CryptoVariables;
import crypt.Configuration;
import crypt.EncryptUtil;

public class ReadEncrypted {

	/**
	 * The main method of the reader.
	 * Decrypt the argument file and verify the signature on the data
	 */
	public static void main(String[] args) throws FileNotFoundException,
			KeyStoreException, NoSuchAlgorithmException, CertificateException,
			IOException, NoSuchPaddingException, InvalidKeyException,
			IllegalBlockSizeException, BadPaddingException,
			ClassNotFoundException, UnrecoverableEntryException,
			InvalidKeySpecException, InvalidAlgorithmParameterException,
			SignatureException {

		if (args.length != 2)
        {
			System.out.println("Read <config_file_path> <decryption_password>");
			return;
		}

		// Read the configuration and signature data
		ConfigContainer<Configuration,byte[]> ConfigData = readConfiguration(args[0]);
        Configuration config = ConfigData.x;
        byte[] sig = ConfigData.y;

		String keystorePassword = args[1];
		
		// Get the cryptographic variables
		CryptoVariables cryptoVariables = readCryptoVariables(config
				.getCryptVarFilePath());
		KeyStore keyStore = EncryptUtil.getKeyStore(keystorePassword);
		Key privateKey = keyStore.getKey(RSA_ALIAS, keystorePassword
                .toCharArray());
		SecretKey secretKey = cryptoVariables.decryptKey(privateKey);

		System.out.println("Read secret key: "
				+ EncryptUtil.getHexDump(secretKey.getEncoded()));
		System.out.println("Read iv: "
				+ EncryptUtil.getHexDump(cryptoVariables.getIV()));

		// Read the data from the file
		FileInputStream fis = new FileInputStream(config.getEncryptedFilePath());
		ObjectInputStream cipherOis = createSecipherInputStream(fis, secretKey,
				cryptoVariables.getIV());

		byte[] plainData = (byte[]) cipherOis.readObject();


		// Close resources
		cipherOis.close();

		System.out.println("Decrypted data: " + new String(plainData));

		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		PublicKey publicKey = keyStore.getCertificate(RSA_ALIAS).getPublicKey();
		signature.initVerify(publicKey);
		signature.update(plainData);
		System.out.println("The Signature is"
				+ (signature.verify(sig) ? "" : "n't") + " valid!");

	}

	/**
	 * Read the DecryptionConfiguration from the FS
	 * 
	 * @param configFilePath The path to the configuration on the FS
	 * @return DecryptionConfiguration
	 */
	private static ConfigContainer<Configuration,byte[]> readConfiguration(
			String configFilePath) throws FileNotFoundException, IOException,
			ClassNotFoundException {
		ObjectInputStream ois = null;
		try {
			FileInputStream fis = new FileInputStream(configFilePath);
			ois = new ObjectInputStream(fis);
            Configuration configuration = (Configuration) ois.readObject();
            byte[] signature = (byte[]) ois.readObject();

            ConfigContainer<Configuration, byte[]> configData = new ConfigContainer<Configuration, byte[]>(configuration,signature);
			return configData;
		} finally {
			ois.close();
		}
	}

	/**
	 * Create the ObjectInputStream to decrypt with, which uses the
	 * CipherInputStream according to the crypto variables
	 * 
	 * @param fis
	 *            the FileInputStream of the encrypted file
	 * @param secretKey
	 *            The SecretKey to decipher with
	 * @param iv
	 *            The IV to use for the decryption
	 * @return ObjectInputStream
	 */
	private static ObjectInputStream createSecipherInputStream(
			FileInputStream fis, SecretKey secretKey, byte[] iv)
			throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, IOException,
			InvalidAlgorithmParameterException {

		Cipher cipher = Cipher.getInstance(DATA_CIPHER_ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
		CipherInputStream cis = new CipherInputStream(fis, cipher);
		return new ObjectInputStream(cis);
	}

	/**
	 * Read an instance of {@link CryptoVariables} from the argument keyFilePath
	 * 
	 * @param keyFilePath
	 *            The path of the FS to read the key from
	 * @return an instance of {@link CryptoVariables}
	 */
	private static CryptoVariables readCryptoVariables(String keyFilePath)
			throws ClassNotFoundException, IOException, InvalidKeyException,
			NoSuchAlgorithmException, NoSuchPaddingException,
			UnrecoverableKeyException, KeyStoreException, CertificateException,
			InvalidKeySpecException, IllegalBlockSizeException,
			BadPaddingException {

		ObjectInputStream ois = null;
		try {
			FileInputStream fis = new FileInputStream(keyFilePath);
			ois = new ObjectInputStream(fis);
			return (CryptoVariables) ois.readObject();
		} finally {
			ois.close();
		}
	}

}
